﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// Utility class for storing object properties and applying them to object instances.
    /// </summary>
    /// <remarks>The class can store values of all properties and single-parameter indexers (collections).</remarks>
    public sealed class ObjectProperties
    {
        #region FieldPermissions.

        IDictionary<string, object> properties = new Dictionary<string, object>();
        LevelTwoDictionary<string, object, object> indexers = new LevelTwoDictionary<string, object, object>();

        #endregion

        #region CTor.

        /// <summary>
        /// Initializes a new instance of <b>ObjectProperties</b> class.
        /// </summary>
        public ObjectProperties()
        { }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets a collection of property names and values.
        /// </summary>
        public IDictionary<string, object> Properties
        {
            get { return this.properties; }
        }

        /// <summary>
        /// Gets a collection of indexer names (collection property which exposes <b>Item</b> property) and values.
        /// </summary>
        public LevelTwoDictionary<string, object, object> Indexers
        {
            get { return this.indexers; }
        }

        #endregion

        #region WriteToObject.

        /// <summary>
        /// Writes property values to the given object.
        /// </summary>
        /// <param name="obj">Object whose propertis are to be set to match the values stored in the <b>ObjectProperties</b> instance.</param>
        /// <remarks>The properties which do not exist in the given object are skipped.</remarks>
        public void WriteToObject(object obj)
        {
            if (properties.Count == 0 && indexers.Count == 0)
                return;

            PropertyInfo[] allProperties = obj.GetType().GetProperties();
            foreach (PropertyInfo prop in allProperties)
            {
                if (!prop.CanWrite)
                    continue;

                ParameterInfo[] indexerArgs = prop.GetIndexParameters();
                if (indexerArgs.Length == 0)
                {
                    // Simple setter.
                    object providedValue = this.properties.ContainsKey(prop.Name) ? this.properties[prop.Name] : null;
                    if (providedValue != null)
                    {
                        object compatibileValue = Convert.ChangeType(providedValue, prop.PropertyType);
                        prop.SetValue(obj, compatibileValue, null);
                    }
                }
                else if (indexerArgs.Length == 1)
                {
                    // Single-parameter indexer.
                    Type argType = indexerArgs[0].ParameterType;
                    IDictionary<object, object> indexesAndValues = this.indexers.Get1stLevelDictionary(prop.Name);
                    foreach (KeyValuePair<object, object> idxAndVal in indexesAndValues)
                    {
                        object compatibileIndexValue = Convert.ChangeType(idxAndVal.Key, argType);
                        object compatibilePropertyValue = Convert.ChangeType(idxAndVal.Value, prop.PropertyType);
                        prop.SetValue(obj, compatibilePropertyValue, new object[] { compatibileIndexValue });
                    }
                }
            }
        }

        /// <summary>
        /// Writes property values to the given object.
        /// </summary>
        /// <param name="obj">Object whose propertis are to be set to match the values stored in the <b>ObjectProperties</b> instance.</param>
        /// <param name="ignoreErrors">Indicates whether all exceptions encountered during writing and type conversion should be ignored.</param>
        /// <remarks>The properties which do not exist in the given object are skipped.</remarks>
        public void WriteToObject(object obj, bool ignoreErrors)
        {
            if (properties.Count == 0 && indexers.Count == 0)
                return;

            PropertyInfo[] allProperties = obj.GetType().GetProperties();
            foreach (PropertyInfo prop in allProperties)
            {
                if (!prop.CanWrite)
                    continue;

                ParameterInfo[] indexerArgs = prop.GetIndexParameters();
                try
                {
                    if (indexerArgs.Length == 0)
                    {
                        // Simple setter.
                        object providedValue = this.properties.ContainsKey(prop.Name) ? this.properties[prop.Name] : null;
                        if (providedValue != null)
                        {
                            object compatibileValue = Convert.ChangeType(providedValue, prop.PropertyType);
                            prop.SetValue(obj, compatibileValue, null);
                        }
                    }
                    else if (indexerArgs.Length == 1)
                    {
                        // Single-parameter indexer.
                        Type argType = indexerArgs[0].ParameterType;
                        IDictionary<object, object> indexesAndValues = this.indexers.Get1stLevelDictionary(prop.Name);
                        foreach (KeyValuePair<object, object> idxAndVal in indexesAndValues)
                        {
                            object compatibileIndexValue = Convert.ChangeType(idxAndVal.Key, argType);
                            object compatibilePropertyValue = Convert.ChangeType(idxAndVal.Value, prop.PropertyType);
                            prop.SetValue(obj, compatibilePropertyValue, new object[] { compatibileIndexValue });
                        }
                    }
                }
                catch
                {
                    if (!ignoreErrors)
                        throw;
                }
            }
        }

        #endregion
    }
}